<?php
/**
 * TinyMVC - MysqlDriver.class.php file
 *
 * @author Adrian Wieczorek <adi(at)tinymvc(dot)org>
 * @package TinyMVC
 * @subpackage Database
 * @version $Id: MysqlDriver.class.php 119 2011-11-05 16:23:41Z adi3ek $
 *
 */

/**
 * This is a simple MySQL database driver, which should be used as a param in {@link Database::getInstance} first call
 * @package TinyMVC
 * @subpackage Database
 */
class MysqlDriver {
	/**
	 * connection handlers
	 * @var array
	 * @access protected
	 */
	protected $connections = array();
	/**
	 * active connection handler
	 * @var resource
	 * @access protected
	 */
	protected $connection = null;
	/**
	 * active connection id
	 * @var string
	 * @access protected
	 */
	protected $connectionId = null;
	/**
	 * transaction blocks counter
	 */
	protected $transactionBlocks = array();
	/**
	 * debug flag
	 */
	protected $debugMode = 0;
	protected $queryLog = array();

	/**
	 * @param string $dbHost mysql database hostname
	 * @param string $dbUser mysql database user
	 * @param string $dbPasswd mysql database user password
	 * @param string $dbName mysql database name
	 */
	public function __construct($dbHost, $dbUser, $dbPasswd, $dbName, $connectionId = 'default') {
		$this->addConnection($connectionId, $dbHost, $dbUser, $dbPasswd, $dbName);
		$this->setConnection($connectionId);
	}

	protected function connect($dbHost, $dbUser, $dbPasswd, $dbName, $newLink = false) {
		$connection = @mysql_connect($dbHost, $dbUser, $dbPasswd, $newLink);

		if(!$connection) {
			throw new TinyMVCException("MysqlDriverException: connect($dbHost, $dbUser, *******) failed.");
		}

		if(!@mysql_select_db($dbName, $connection)) {
			throw new TinyMVCException("MysqlDriverException: cannot use database: $dbName");
		}

		$this->log("/* Connected to: $dbUser@$dbHost/$dbName */");
		return $connection;
	}

	protected function disconnect($connectionId) {
		if(isset($this->connections[$connectionId])) {
			@mysql_close($this->connections[$connectionId]);
			unset($this->connections[$connectionId]);
			unset($this->transactionBlocks[$connectionId]);
		}
	}

	/**
	 * reconnect given conneciton
	 * @param string $connectionId connection identifier
	 */
	public function reconnect($connectionId) {
		$credentials = $this->connections[$connectionId]['credentials'];
		$this->addConnection($connectionId, $credentials['dbHost'], $credentials['dbUser'], $credentials['dbPasswd'], $credentials['dbName']);
		if($this->connectionId == $connectionId) {
			// replace current connection when necessary
			$this->connectionId = null;
			$this->setConnection($connectionId);
		}
	}

	/**
	 * add another connection
	 * @param string $connectionId connection identifier
	 * @param string $dbHost mysql database hostname
	 * @param string $dbUser mysql database user
	 * @param string $dbPasswd mysql database user password
	 * @param string $dbName mysql database name
	 */
	public function addConnection($connectionId, $dbHost, $dbUser, $dbPasswd, $dbName) {
		$this->connections[$connectionId] = array();
		$this->connections[$connectionId]['handler'] = $this->connect($dbHost, $dbUser, $dbPasswd, $dbName, true);
		$this->connections[$connectionId]['credentials'] = array('dbHost' => $dbHost, 'dbUser' => $dbUser, 'dbPasswd' => $dbPasswd, 'dbName' => $dbName);
		$this->transactionBlocks[$connectionId] = 0;
	}

	/**
	 * remove and close given db connction
	 * @param string $connectionId connection identifier
	 */
	public function delConnection($connectionId) {
		$this->disconnect($connectionId);
	}

	/**
	 * remove and close all db connections
	 * @param array $keepAliveConnections list of connections which will remain open
	 */
	public function delConnections($keepAliveConnections = array()) {
		foreach($this->connections as $connectionId => $connection) {
			if(!in_array($connectionId, $keepAliveConnections)) {
				$this->disconnect($connectionId);
			}
		}
	}

	/**
	 * set active connection
	 * @param string $connectionId connection identifier
	 */
	public function setConnection($connectionId) {
		if($this->connectionId != $connectionId) {
			$this->connection = $this->connections[$connectionId]['handler'];
			$this->connectionId = $connectionId;
			$this->log("/* Using connection ID: $connectionId */");
		}
	}

	/**
	 * check if connection exists
	 * @param string $connectionId connection identifier
	 */
	public function isConnection($connectionId) {
		return isset($this->connections[$connectionId]);
	}

	/**
	 * perform database query
	 * @param string $sql SQL query string
	 * @return MysqlResult
	 */
	public function query($sql) {
		$result = null;
		$result = @mysql_query($sql, $this->connection);
		$this->log($sql);

		if(!$result) {
			$error = mysql_error($this->connection);
			throw new TinyMVCException('MysqlDriverException: invalid SQL: ' . $sql . ' <br /><b>Mysql Error:</b> ' . $error .'<br />');
		}
		return new MysqlResult($result, $this->connection);
	}

	public function executeSqlFile($fileName) {
		if(file_exists($fileName)) {
			$fileContent = file_get_contents($fileName);
			foreach(explode(';', $fileContent) as $sqlQuery) {
				$sqlQuery = trim($sqlQuery);
				if(!empty($sqlQuery)) {
					$this->query($sqlQuery);
				}
			}
		}
		else {
			throw new TinyMVCException('MysqlDriverExeption: SQL file: ' . $fileName . ' not found.');
		}
	}

	/**
	 * begin transaction (alias for backward compatibility)
	 */
	public function beginTransaction() {
		$this->begin();
	}

	/**
	 * begin db transaction
	 */
	public function begin() {
		if($this->transactionBlocks[$this->connectionId] == 0) {
			$this->query("BEGIN");
			$this->transactionBlocks[$this->connectionId]++;
			$this->log('/* Transaction block started (' . $this->connectionId . ' LEVEL=' . $this->transactionBlocks[$this->connectionId] . ') */');
		}
		else {
			$this->transactionBlocks[$this->connectionId]++;
			$this->log('/* Nested trascation block started (' . $this->connectionId . ' LEVEL=' . $this->transactionBlocks[$this->connectionId] . ') */');
		}
	}

	/**
	 * commit transaction (alias for backward compatibility)
	 */
	public function endTransaction() {
		$this->commit();
	}

	/**
	 * commit transaction
	 * @param bool $forceCommit force commit even though it's nested transaction block
	 */
	public function commit($forceCommit = false) {
		$this->log('/* Transaction block ended (' . $this->connectionId . ' LEVEL=' . $this->transactionBlocks[$this->connectionId] . ') */');
		$this->transactionBlocks[$this->connectionId]--;
		if(($this->transactionBlocks[$this->connectionId] <= 0) || $forceCommit) {
			$this->transactionBlocks[$this->connectionId] = 0;
			// commit only when there's no nested blocks
			$this->query("COMMIT");
		}
	}

	/**
	 * rollback transaction
	 */
	public function rollback() {
		$this->query("ROLLBACK");
		$this->transactionBlocks[$this->connectionId] = 0;
	}

	/**
	 * set debug flag
	 * @param int $mode
	 */
	public function setDebugMode($mode) {
		$this->debugMode = $mode;
	}

	/**
	 * return debug flag
	 */
	public function getDebugMode() {
		return $this->debugMode;
	}

	/**
	 * return query log
	 */
	public function getDebugLog() {
		return $this->queryLog;
	}

	public function clearDebugLog() {
		$this->queryLog = array();
	}

	public function log($string, $level = 1) {
		if($this->debugMode >= $level) {
			$this->queryLog[] = $string;
		}
	}
}
